/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/


package com.chasekernan.hxnova.core.races;

import com.chasekernan.hxnova.core.stars.Environment;
import com.chasekernan.hxnova.core.stars.Star;

/**
    Class containing static methods to generate the advantage points for a race.
**/
class GeneratePoints {
    
    public static var STARTING_POINTS = 1650.0;
    public static var HAB_MODIFIER = 2000;
    public static var SMALL_GROWTH_RATE = 5;
    public static var SMALL_GROWTH_BENEFIT = 4200;
    public static var MED_GROWTH_RATE = 13;
    public static var LARGE_GROWTH_RATE = 19;
    public static var MAX_GROWTH_FACTOR = 45;
    public static var HAB_AND_GROWTH_MODIFIER = 24;
    public static var OFF_CENTER_HAB_POINTS = 4;
    public static var MORE_THAN_ONE_IMMUNITY = 150;
    public static var FACTORY_PRODUCTION_MODIFIER = 2;
    public static var HE_FACTORY_PRODUCTION_MODIFIER = 3;
    public static var LIMIT_ON_LOW_FACTORY_POINTS = 700;
    public static var FACTORIES_COST_LESS_MIN_PENALTY = 175;
    public static var EXTRA_TECH_PENALITY = 180;
    
    
    /**
        Returns the advantage points of a race.
        Thanks to ConstB and the Freestars project for the math (and code) 
        behind this.
    **/
    public static function generate(race : Race) : Int {
        var points = STARTING_POINTS;
        
        var habPoints = Std.int(getHabPoints(race) / HAB_MODIFIER);
        
        var growthRateFactor = race.growthRate;
        var growthRate = growthRateFactor;
        
        if (growthRate <= SMALL_GROWTH_RATE) {
            points += (SMALL_GROWTH_RATE + 1 - growthRate) * 
                      SMALL_GROWTH_BENEFIT;
                      
        } else if (growthRate <= MED_GROWTH_RATE) {
            points += switch(growthRate) {
                case 6: 3600; 
                case 7: 2250;
                case 8: 600;
                case 9: 225;
            }
            
            growthRateFactor = growthRateFactor * 2 - 5;
            
        } else if (growthRate <= LARGE_GROWTH_RATE) {
            growthRateFactor = (growthRateFactor - 6) * 3;
            
        } else growthRateFactor = MAX_GROWTH_FACTOR;
        
        points -= (habPoints * growthRateFactor) / HAB_AND_GROWTH_MODIFIER;
        
        var immunities = 0; //not implemented yet
        
        for (habPart in race.habSetting) {
            points += Math.abs(habPart.middle - 50) * OFF_CENTER_HAB_POINTS;
        }
        
        if (immunities > 1) points -= MORE_THAN_ONE_IMMUNITY;
        
        var operableFactoriesPoints = race.operableFactories;
        var factoryProductionPoints = race.factoryProduction;
        
        if (operableFactoriesPoints > 10 || factoryProductionPoints > 10) {
            operableFactoriesPoints -= 9;
            if (operableFactoriesPoints < 1) operableFactoriesPoints = 1;
            
            factoryProductionPoints -= 9;
            if (factoryProductionPoints < 1) factoryProductionPoints = 1;
            
            factoryProductionPoints *= switch(race.prt) {
                case HyperExpansion: HE_FACTORY_PRODUCTION_MODIFIER;
                default: FACTORY_PRODUCTION_MODIFIER;
            };
            
            //penalty from two and three immune races added here
            points -= ((factoryProductionPoints * operableFactoriesPoints) * 
                        growthRate) / if (immunities >= 2) 2 else 9;
                        
        }
        
        var popProduction = Std.int(race.popProduction / 100);
        if (popProduction < 7) popProduction = 7;
        
        points -= switch(popProduction) {
            case 7: 2400;
            case 8: 1200;
            case 9: 600;
            default: -(popProduction - 10) * 120;
        };
        
        //need an if(AR) here when AR is implemented
        
        //first factories
        var prodPoints = race.factoryProduction - 10;
        var costPoints = race.factoryCost - 10;
        var operPoints = race.operableFactories - 10;
        var tmpPoints = 0.0;
        
        tmpPoints -= prodPoints * if (prodPoints < 0) 100 else 121;
        tmpPoints += costPoints * if (costPoints < 0) costPoints * -60 else 55;
        tmpPoints -= operPoints * if (operPoints < 0) 40 else 35;
        
        var limit = LIMIT_ON_LOW_FACTORY_POINTS; //just to shorten the name here
        if (tmpPoints > limit) tmpPoints = (tmpPoints - limit) / 3 + limit;
        
        tmpPoints -= if (operPoints > 14) 360
                     else if (operPoints > 11) (operPoints - 7) * 45
                     else if (operPoints >= 7) (operPoints - 6) * 30
                     else 0; //need this here for compiler
                     
        if (prodPoints >= 3) tmpPoints -= (prodPoints - 2) * 60;
        
        points += tmpPoints;
        
        if (race.factoriesCostLessMin) {
            points -= FACTORIES_COST_LESS_MIN_PENALTY;
        }
        
        //now mines
        prodPoints = 10 - race.mineProduction;
        costPoints = 3 - race.mineCost;
        operPoints = 10 - race.operableMines;
        
        points += prodPoints * if (prodPoints > 0) 100 else 169;
        points += if (costPoints > 0) -360 else 80 - costPoints * 65;
        points += operPoints * if (operPoints > 0) 40 else 35;
        
        points -= PRTSet.getCost(race.prt);
        
        var goodLRTs = 0; //costs > 0 (ie benefit for face)
        var badLRTs = 0; //costs < 0 (ie disadvantage for race)
        for (lrt in race.lrts) {
            var cost = LRTSet.getCost(lrt);
            
            points -= cost;
            if (cost < 0) badLRTs++;
            else goodLRTs++;
        }
        
        //penalty for too many lrts
        points -= if (badLRTs + goodLRTs > 4) 
                        (badLRTs + goodLRTs) * (badLRTs + goodLRTs - 4) * 10
                  else if (badLRTs - goodLRTs > 3)
                        (badLRTs - goodLRTs - 3) * 60
                  else if (goodLRTs - badLRTs > 3) 
                        (goodLRTs - badLRTs - 3) * 40;
                  else 0; //for compiler
                        
         var techCosts = race.researchCosts.getFieldsCheap() - 
                         race.researchCosts.getFieldsExpensive();
                         
         if (techCosts > 0) {
             points -= techCosts * techCosts * 130;
             points += if (techCosts >= 6) 1430 else if (techCosts == 5) 520;
             
         } else if (techCosts < 0) { 
             techCosts = -techCosts;
             
             points += techCosts * (techCosts + 9) * 15;
             if (techCosts >= 6) points += 30;
             if (techCosts > 4 && race.popProduction < 1000) points -= 190;
         }
         
         if (race.startsWithExtraTech) points -= EXTRA_TECH_PENALITY;
         
         return Std.int(points / 3);
    }
    
    //porting from c++ makes this really ugly... it needs cleaning
    private static function getHabPoints(race : Race) : Float {
        var points = 0.0; //to make it capable of storing large nums
        var isTotalTerraforming = false; //not implemented yet
        
        var terraformingFactor = 0;
        
        var testStar = new Star();
        var testEnvironment = new Environment(false);
        testStar.environment = testEnvironment;
        
        var habCenters = new Array<Int>();
        for (habPart in race.habSetting) {
            habCenters.push(Std.int(habPart.middle));
        }
        
        
        for (h in 0...3) {
            var testHabStart = new Array<Int>();
            var testHabWidth = new Array<Int>();
            var iterationNum = new Array<Int>();
            var terraformingDone = new Array<Int>();
            
            terraformingFactor = switch(h) {
                case 0: 0;
                case 1: if (isTotalTerraforming) 8 else 5;
                default: if (isTotalTerraforming) 17 else 15;
            }
            
            for (habPart in race.habSetting) {
                testHabStart.push(
                        max(0, habPart.lowerBound - terraformingFactor));
                        
                var testHab = min(100, habPart.upperBound + terraformingFactor);
                testHabWidth.push(
                        testHab - testHabStart[testHabStart.length - 1]);
                
                iterationNum.push(11);
            }
            
            //this is done one field at a time because const array access can
            //be optimized by haXe and its easier to port it this way from 
            //ConstB's code
            var habSum1 = 0.0;
            
            for (i in 0...iterationNum[0]) {
                var tmpHab1 = testHabStart[0] + 
                if (i == 0 || iterationNum[0] <= 1) 0
                else Std.int((testHabWidth[0] * i) / (iterationNum[0] - 1));
                
                if (h != 0) { //and its not immune...
                    var habDiff = habCenters[0] - tmpHab1;
                    
                    if (Math.abs(habDiff) <= terraformingFactor) habDiff = 0;
                    else if (habDiff < 0) habDiff += terraformingFactor;
                    else habDiff -= terraformingFactor;
                    
                    terraformingDone[0] = habDiff;
                    tmpHab1 = habCenters[0] - habDiff;
                }
                
                testEnvironment.values[0] = tmpHab1;
                
                var habSum2 = 0.0;
                
                for (j in 0...iterationNum[1]) {
                    var tmpHab2 = testHabStart[1] + 
                    if (j == 0 || iterationNum[1] <= 1) 0
                    else Std.int((testHabWidth[1] * j) / (iterationNum[1] - 1));
                    
                    if (h != 0) { //and its not immune...
                        var habDiff = habCenters[1] - tmpHab2;
                    
                        if (Math.abs(habDiff) <= terraformingFactor) {
                            habDiff = 0;
                        } else if (habDiff < 0) {
                            habDiff += terraformingFactor;
                        } else {
                            habDiff -= terraformingFactor;
                        }
                    
                        terraformingDone[1] = habDiff;
                        tmpHab2 = habCenters[1] - habDiff;
                    }
                
                    testEnvironment.values[1] = tmpHab2;
                    
                    var habSum3 = 0;
                    
                    for (k in 0...iterationNum[2]) {
                        var tmpHab3 = testHabStart[2] + 
                        if (k == 0 || iterationNum[2] <= 1) 0
                        else {
                            Std.int(
                                (testHabWidth[2] * k) / (iterationNum[2] - 1));
                        }
                    
                        if (h != 0) { //and its not immune...
                            var habDiff = habCenters[2] - tmpHab3;
                    
                            if (Math.abs(habDiff) <= terraformingFactor) {
                                habDiff = 0;
                            } else if (habDiff < 0) {
                                habDiff += terraformingFactor;
                            } else {
                                habDiff -= terraformingFactor;
                            }
                    
                            terraformingDone[2] = habDiff;
                            tmpHab3 = habCenters[2] - habDiff;
                        }
                
                        testEnvironment.values[2] = tmpHab3;
                        
                        var desire = Std.int(
                                testStar.getHabPercent(race) * 100.0);
                                
                                
                        var sum = 0;
                        for (amount in terraformingDone) sum += amount;
                        if (sum > terraformingFactor) {
                            desire -= sum - terraformingFactor;
                            if (desire < 0) desire = 0;
                        }
                        
                        desire *= desire;
                        desire *= switch (h) {
                            case 0: 7;
                            case 1: 5;
                            default: 6;
                        }
                        
                        habSum3 += desire;
                        
                    }
                    
                    //if not immune
                    habSum3 = Std.int((habSum3 * testHabWidth[2]) / 100);
                    
                    habSum2 += habSum3;
                    
                }
                
                //if not immune
                habSum2 = (habSum2 * testHabWidth[1]) / 100;
                
                habSum1 += habSum2;
            }
            
            //if not immune
            habSum1 = (habSum1 * testHabWidth[0]) / 100;
            
            points += habSum1;
        }
        
        return points / 10.0 + 0.5;
    }
    
    private static inline function min(n1 : Int, n2 : Int) : Int {
        return if (n1 < n2) n1 else n2;
    }
    
    private static inline function max(n1 : Int, n2 : Int) : Int {
        return if (n1 > n2) n1 else n2;
    }
    
    //returns the number that is percent % towards n2 from n1.
    //example percentage(10, 20, 0.5) = 15
    private static inline function percentage(n1 : Int, n2 : Int, 
                                              percent : Float) : Int {
        return Std.int(n1 + percent * (n2 - n1));
    }
        
}